#include "NetworkPing.h"

NetworkPing::NetworkPing() :
    QThread(),
    ICMPData(nullptr),
    u16_packetSeq(0)
{
    qRegisterMetaType<NetworkPingState>("NetworkPingState");
}

NetworkPing::~NetworkPing()
{
#ifdef _WIN32
    WSACleanup();
#endif

    if (ICMPData != nullptr)
    {
        free(ICMPData);
        ICMPData = nullptr;
    }
}

bool NetworkPing::initSocket()
{
#ifdef _WIN32
    WSADATA WSAData;
    if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0)
    {
        qDebug() << "WSAStartup() failed: " <<  WSAGetLastError();
        return false;
    }
    u16_currentProcID = (uint16_t)GetCurrentProcessId();//获取当前进程ID
    sockfd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, nullptr, 0, WSA_FLAG_OVERLAPPED);
    if (sockfd == INVALID_SOCKET)
    {
        qDebug() << "WSASocket() failed: " <<  WSAGetLastError();
        return false;
    }
    else
    {
        if (ICMPData) {
            free(ICMPData);
            ICMPData = nullptr;
        }

        ICMPData = (char *)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader));

        if (ICMPData == nullptr)
        {
            return false;
        }
    }
#else
    u16_currentProcID = getpid();

    if (sockfd) {
        close(sockfd);
    }

    if(u16_currentProcID)
#ifdef __linux__
        sockfd = socket(PF_INET,SOCK_RAW, IPPROTO_ICMP);
#else
        sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
#endif
    else
        sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

    if (sockfd < 0) {
        qDebug() << "socket error";
        return false;
    }

    if (u16_currentProcID < 0)
    {
        qDebug() << "sockfd faild!" << errno;
        return false;
    } else {
        if (ICMPData) {
            free(ICMPData);
            ICMPData = nullptr;
        }
        ICMPData = (char *)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader));

        if (ICMPData == nullptr)
        {
            return false;
        }
    }
#endif
    return true;
}

bool NetworkPing::privatePing(char *destIP, PingReply *pPingReply, uint32_t u32_timeOut)
{
    if (destIP != nullptr)
    {
        return privatePingCore(inet_addr(destIP), pPingReply, u32_timeOut);
    }
    else
    {
        qDebug() << "url or ip error!";
    }
    return false;
}

bool NetworkPing::privatePingCore(uint32_t u32_destIP, PingReply *pPingReply, uint32_t u32_timeOut)
{
    sockaddr_in sockaddrDest;
    sockaddrDest.sin_family = AF_INET;
    sockaddrDest.sin_addr.s_addr = u32_destIP;
    int sockaddrDestSize = sizeof(sockaddrDest);

    int ICMPDataSize = DEF_PACKET_SIZE + sizeof(ICMPHeader);
    uint32_t u32_sendTimeStamp = getCurrentTime();
    uint16_t u16_seq = u16_packetSeq++;
    memset(ICMPData, 0, ICMPDataSize);//将ICMPData指向地址开始ICMPDataSize大小填充0
    ICMPHeader *pICMPHeader = (ICMPHeader *)ICMPData;

    //设置ICMPHeader
    pICMPHeader->u8_type = ECHO_REQUEST;
    pICMPHeader->u8_code = 0;
    pICMPHeader->u16_ID = u16_currentProcID;
    pICMPHeader->u16_seq = u16_seq;
    pICMPHeader->u32_timeStamp = u32_sendTimeStamp;
    pICMPHeader->u16_checkSum = calcCheckSum((uint16_t *)ICMPData, ICMPDataSize);

    sendto(sockfd, ICMPData, ICMPDataSize, 0, (struct sockaddr *)&sockaddrDest, sockaddrDestSize);

    sendCount++;

    char recvbuf[256] = {"\0"};

    while (true)
    {
        //经socket接收数据
        int packetSize = recvfrom(sockfd, recvbuf, 256, 0, (struct sockaddr *)&sockaddrDest, (socklen_t *)&sockaddrDestSize);

        if (packetSize != SOCKET_ERROR)
        {
            IPHeader *pIPHeader = (IPHeader *)recvbuf;
            uint16_t u16_IPHeaderLen = (uint16_t)((pIPHeader->u8_VerHLen & 0x0f) * 4);
            ICMPHeader *pICMPHeader = (ICMPHeader *)(recvbuf + u16_IPHeaderLen);
            if (pICMPHeader->u16_ID == u16_currentProcID && pICMPHeader->u8_type == ECHO_REPLY && pICMPHeader->u16_seq == u16_seq)
            {
                receiveCount++;
                uint32_t u32_recvTimeStamp = getCurrentTime();
                pPingReply->u16_seq = u16_seq;
                pPingReply->u32_roundTripTime = u32_recvTimeStamp - pICMPHeader->u32_timeStamp;
                pPingReply->u32_bytes = packetSize - u16_IPHeaderLen - sizeof(ICMPHeader);
                pPingReply->u32_TTL = pIPHeader->u8_TTL;

                if (shortestTime > pPingReply->u32_roundTripTime)
                {
                    shortestTime = pPingReply->u32_roundTripTime;
                }

                if (longestTime < pPingReply->u32_roundTripTime)
                {
                    longestTime = pPingReply->u32_roundTripTime;
                }

                totalTime += pPingReply->u32_roundTripTime;
                //                avgTime = totalTime / receiveCount;
                return true;
            }
        }

        if (getCurrentTime() - u32_sendTimeStamp >= u32_timeOut)
        {
            qDebug() << "ping timeout!";
            return false;
        }

        QThread::msleep(100);
    }
}

uint16_t NetworkPing::calcCheckSum(uint16_t *pBuffer, int size)
{
    unsigned long ulCheckSum = 0;
    while (size > 1)
    {
        ulCheckSum += *pBuffer++;
        size -= sizeof(USHORT);
    }
    if (size)
    {
        ulCheckSum += *(UCHAR *)pBuffer;
    }

    ulCheckSum = (ulCheckSum >> 16) + (ulCheckSum & 0xffff);
    ulCheckSum += (ulCheckSum >> 16);

    return (USHORT)(~ulCheckSum);
}

uint32_t NetworkPing::getCurrentTime()
{
#ifdef _WIN32
    static ULONG s_ulFirstCallTick = 0;
    static LONGLONG s_ullFirstCallTickMS = 0;

    SYSTEMTIME systemtime;
    FILETIME filetime;
    GetLocalTime(&systemtime);
    SystemTimeToFileTime(&systemtime, &filetime);
    LARGE_INTEGER liCurrentTime;
    liCurrentTime.HighPart = filetime.dwHighDateTime;
    liCurrentTime.LowPart = filetime.dwLowDateTime;
    LONGLONG llCurrentTimeMS = liCurrentTime.QuadPart / 10000;

    if (s_ulFirstCallTick == 0)
    {
        s_ulFirstCallTick = GetTickCount();
    }
    if (s_ullFirstCallTickMS == 0)
    {
        s_ullFirstCallTickMS = llCurrentTimeMS;
    }

    return s_ulFirstCallTick + (ULONG)(llCurrentTimeMS - s_ullFirstCallTickMS);
#else
    struct timeval tv;
    gettimeofday(&tv, nullptr);
    return (uint32_t)(tv.tv_sec * 1000 + tv.tv_usec / 1000);
#endif
}

uint8_t NetworkPing::getSendCount() const
{
    return sendCount;
}

uint8_t NetworkPing::getReceiveCount() const
{
    return receiveCount;
}

uint8_t NetworkPing::getPacketLossRate() const
{
    return packetLossRate;
}

uint32_t NetworkPing::getPacketLossBytes() const
{
    return packetLossBytes;
}

uint32_t NetworkPing::getShortestTime() const
{
    return shortestTime;
}

uint32_t NetworkPing::getLongestTime() const
{
    return longestTime;
}

uint32_t NetworkPing::getAvgTime() const
{
    return avgTime;
}

void NetworkPing::ping(const QList<std::string> list, uint32_t timeOut)
{
    this->urls = list;
    this->timeOut   = timeOut;

    start();
    m_isQuit = false;
}

void NetworkPing::run()
{
    while (!m_isQuit) {
        NetworkPingState state = syncPing(urls.at((m_index = ++m_index % urls.size())),timeOut);
        emit pingState(state);

        QThread::msleep(2000);
    }
}

NetworkPingState NetworkPing::syncPing(const std::string &url, uint32_t timeOut)
{
    if(!initSocket())
    {
        qDebug() << u8"socket init error";
        return NETWORK_INIT_ERROR;
    }

#ifdef _WIN32
    int tv = timeOut;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
#else
    struct timeval tv;
    tv.tv_sec = timeOut / 1000;
    tv.tv_usec = timeOut % 1000 * 1000;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
#endif

    PingReply reply;

    struct hostent *remoteHost;
    //这里如果没有联网无法解析
    remoteHost = gethostbyname(url.c_str());

    if (remoteHost == 0) {
        qDebug() << "error invalid url";
        return NETWORK_NAME_ERROR;
    }
    char destIP[20];
    memcpy(&destIP, inet_ntoa(*((struct in_addr *)remoteHost->h_addr_list[0])), 20);
//        qDebug() << u8"正在Ping" << url.c_str() << u8" 具有 " << DEF_PACKET_SIZE << u8" 字节的数据:";

    for (int i = 0; i < 1; i++)
    {
        if (!privatePing(destIP, &reply, timeOut))
        {
#ifdef DEBUG
            qDebug() << "ping error" << url.c_str();
#endif
            return NETWORK_PING_ERROR;
        }

        //        qDebug() << u8"来自 " << destIP << u8" 的回复: 字节 = " << reply.u32_bytes << u8" 时间 = " << reply.u32_roundTripTime << " TTL = " << reply.u32_TTL;
    }

    //    qDebug() << destIP << u8" 的 Ping 统计信息:";
    //    qDebug() << u8"    数据包：已发送 = " << getSendCount() << u8" , 已接收 = " << getReceiveCount() << u8" , 丢失 = " << getSendCount() - getReceiveCount() << " ( " << (getSendCount() - getReceiveCount()) / getSendCount() * 100 << u8" %丢失)";

    //    if ((getSendCount() - getReceiveCount()) / getSendCount() != 1)
    //    {
    //        qDebug() << u8"往返行程的估计时间(以毫秒为单位):";
    //        qDebug() << u8"    最短 = " << getShortestTime() << u8" ms，最长 = " << getLongestTime() << u8" ms，平均 = " << getAvgTime() <<" ms";

    //        return NETWORK_PING_ERROR;
    //    }

    return NETWORK_PING_OK;
}

void NetworkPing::exitPing()
{
    m_isQuit = true;
}
